﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Bonobo.Irc
{
    [Serializable]
    public sealed class IrcMessage : ICloneable
    {
        private readonly ArgumentCollection _arguments;
        private readonly Boolean _readOnly;

        private String _prefix;
        private String _command;

        public IrcMessage(String prefix, String command, params String[] arguments)
            : this(prefix, command, false, arguments)
        {
        }

        private IrcMessage(String prefix, String command, Boolean readOnly, IEnumerable<String> arguments)
        {
            if (String.IsNullOrEmpty(command))
            {
                throw new ArgumentException(Resources.IrcMessage_ValueNullOrEmpty, "command");
            }

            _arguments = new ArgumentCollection(this);
            _command = String.Intern(command);

            if (!String.IsNullOrEmpty(prefix))
            {
                _prefix = prefix;
            }

            if (arguments != null)
            {
                _arguments.AddRange(arguments);
            }

            _readOnly = readOnly;
        }

        public IList<String> Arguments
        {
            get { return _arguments; }
        }
        
        public String Command
        {
            get { return _command; }
            set
            {
                EnsureNotReadOnly();

                if (String.IsNullOrEmpty(value))
                {
                    throw new ArgumentException(Resources.IrcMessage_ValueNullOrEmpty, "value");
                }

                _command = String.Intern(value);
            }
        }

        public String Prefix
        {
            get { return _prefix; }
            set
            {
                EnsureNotReadOnly();

                if (String.IsNullOrEmpty(value))
                {
                    _prefix = null;
                }
                else
                {
                    _prefix = value;
                }
            }
        }

        public IrcMessage AsReadOnly()
        {
            if (_readOnly)
            {
                return this;
            }

            return new IrcMessage(_prefix, _command, true, _arguments);
        }

        public IrcMessage Clone()
        {
            return new IrcMessage(_prefix, _command, _readOnly, _arguments);
        }

        public Boolean IsCommand(String cmd)
        {
            return StringComparer.Ordinal.Equals(_command, cmd);
        }

        public Byte[] ToByteArray()
        {
            var str = ToString();
            return IrcProtocol.Encoding.GetBytes(str);
        }

        public override String ToString()
        {
            var sb = new StringBuilder();

            if (!String.IsNullOrEmpty(_prefix))
            {
                sb.Append(IrcProtocol.Colon);
                sb.Append(_prefix);
                sb.Append(IrcProtocol.Space);
            }

            sb.Append(_command);

            foreach (var arg in _arguments)
            {
                sb.Append(IrcProtocol.Space);

                if (arg.Contains(IrcProtocol.Space))
                {
                    sb.Append(IrcProtocol.Colon);
                }

                sb.Append(arg);
            }

            sb.Append(IrcProtocol.NewLine);

            return sb.ToString();
        }

        public void WriteTo(Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            var buffer = ToByteArray();
            stream.Write(buffer, 0, buffer.Length);
        }

        Object ICloneable.Clone()
        {
            return Clone();
        }

        private void EnsureNotReadOnly()
        {
            if (_readOnly)
            {
                throw new InvalidOperationException(Resources.IrcMessage_ReadOnly);
            }
        }

        public static IrcMessage ReadOnly(String prefix, String command, params String[] arguments)
        {
            return new IrcMessage(prefix, command, true, arguments);
        }

        public static Boolean TryParseAsReadOnly(Byte[] buffer, Int32 index, Int32 count, out IrcMessage result)
        {
            var str = IrcProtocol.Encoding.GetString(buffer, index, count);
            return TryParseAsReadOnly(str, out result);
        }

        public static Boolean TryParseAsReadOnly(String value, out IrcMessage result)
        {
            return TryParse(value, true, out result);
        }

        public static Boolean TryParse(String value, out IrcMessage result)
        {
            return TryParse(value, false, out result);
        }

        private static Boolean TryParse(String value, Boolean readOnly, out IrcMessage result)
        {
            if (!String.IsNullOrEmpty(value))
            {
                var match = ParsingRegex.Match(value);

                if (match.Success)
                {
                    var prefix = match.Groups["Prefix"].Value;
                    var command = match.Groups["Command"].Value;
                    var args = ParseArguments(match.Groups["Arguments"].Value);

                    if ((args != null) && !String.IsNullOrEmpty(command))
                    {
                        result = new IrcMessage(prefix, command, readOnly, args);
                        return true;
                    }
                }
            }

            result = null;
            return false;
        }

        private static String[] ParseArguments(String args)
        {
            var result = new List<String>();
            var sb = new StringBuilder();

            for (int i = 0; i < args.Length; i++)
            {
                if (args[i] == IrcProtocol.Colon)
                {
                    if (i < args.Length - 1)
                    {
                        result.Add(args.Substring(i + 1));
                    }

                    break;
                }

                if (args[i] == IrcProtocol.Space)
                {
                    if (sb.Length > 0)
                    {
                        result.Add(sb.ToString());
                        sb.Length = 0;
                    }
                }
                else
                {
                    sb.Append(args[i]);
                }
            }

            if (sb.Length > 0)
            {
                result.Add(sb.ToString());
            }

            if (result.Count > IrcProtocol.MaxArgumentCount)
            {
                return null;
            }

            return result.ToArray();
        }

        private static readonly Regex ParsingRegex = new Regex(
            @"(?x)(?s)(?n)
              ^
              ( :    (?<Prefix>    [^\x20]+  )          \x20 )?
                     (?<Command>   [a-zA-Z]+ | [0-9]{3}      )
              ( \x20 (?<Arguments> [^\r]+    )               )? 
                     ( \r\n | \r | \n                        )
              $
            ", 
             RegexOptions.Compiled | RegexOptions.CultureInvariant);

        [Serializable]
        private sealed class ArgumentCollection : Collection<String>
        {
            [NonSerialized]
            private readonly IrcMessage _message;

            public ArgumentCollection(IrcMessage message)
            {
                _message = message;
            }

            public void AddRange(IEnumerable<String> collection)
            {
                foreach (var item in collection)
                {
                    Add(item);
                }
            }

            protected override void ClearItems()
            {
                _message.EnsureNotReadOnly();
                base.ClearItems();
            }

            protected override void InsertItem(Int32 index, String item)
            {
                _message.EnsureNotReadOnly();

                if (String.IsNullOrEmpty(item))
                {
                    throw new ArgumentException(Resources.IrcMessage_ValueNullOrEmpty, "item");
                }

                if (Count == IrcProtocol.MaxArgumentCount)
                {
                    throw new InvalidOperationException(Resources.IrcMessage_MaxNumberOfArgsExceeded);
                }

                if (item.Contains(IrcProtocol.Space))
                {
                    if ((index != Count) || (this.Any(s => s.Contains(IrcProtocol.Space))))
                    {
                        throw new ArgumentException(Resources.IrcMessage_OnlyLastArgumentCanContainSpaces, "item");
                    }
                }

                base.InsertItem(index, item);
            }

            protected override void RemoveItem(Int32 index)
            {
                _message.EnsureNotReadOnly();
                base.RemoveItem(index);
            }

            protected override void SetItem(Int32 index, String item)
            {
                _message.EnsureNotReadOnly();

                if (String.IsNullOrEmpty(item))
                {
                    throw new ArgumentException(Resources.IrcMessage_ValueNullOrEmpty, "item");
                }

                if (item.Contains(IrcProtocol.Space))
                {
                    if (index != Count - 1)
                    {
                        throw new ArgumentException(Resources.IrcMessage_OnlyLastArgumentCanContainSpaces, "item");
                    }
                }

                base.SetItem(index, item);
            }
        }
    }
}
